package model.newmodel;

import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

//package searcherPackage.Index;
//
//import java.util.Iterator;
//import java.util.Set;
//import java.util.StringTokenizer;
//import java.util.TreeSet;

/**
 * 
 * @author i_khri
 */
public class PostingsList implements Iterable<PostingsList.Entry> {

	/**
	 * Class constructor. Create a new tree set upon construction.
	 */
	public PostingsList() {
		entries = new TreeSet<Entry>();
	}

	/**
	 * 
	 * @param theString
	 *            create a posting list from this string.
	 * @return the posting list formed from the given string
	 */
	public static PostingsList fromIndexString(String theString) {
		PostingsList pList = new PostingsList();

		int beginIndex = theString.indexOf('[');
		int endIndex = theString.indexOf(']');

		String szPostingsList = theString.substring(beginIndex + 1, endIndex);
		szPostingsList = szPostingsList.replace(',', ' ');

		StringTokenizer st = new StringTokenizer(szPostingsList);
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			String[] arr = token.split(":");
			int post = Integer.parseInt(arr[0]);
			double tf = Double.parseDouble(arr[1]);
			pList.addEntry(new PostingsList.Entry(post, tf));
		}

		return pList;
	}

	public static PostingsList intersection(PostingsList pl1, PostingsList pl2) {
		PostingsList result = new PostingsList();
		if (pl1 != null) {
			if (pl2 != null) {
				result.entries.addAll(pl1.entries);
				result.entries.retainAll(pl2.entries);
				return result;
			} else { // pl1 != null, pl2 == null
				return pl1;
			}
		} else {
			if (pl2 != null) { // pl1 == null, pl2 != null
				return pl2;
			} else {
				return result; // return empty postings list if both parameters
								// are null
			}
		}
	}

	/**
	 * @param pl1
	 *            posting list to unite
	 * @param pl2
	 *            posting list to unite
	 * @return the united lists Binary union operation based on Java Set's
	 *         {@code addAll} method. Entries are added based only on equality
	 *         of document id's. Other information in {@code PostingsList.Entry}
	 *         is ignored. As a result, if both postings lists contain entries
	 *         with same document id, for example, term frequencies for entry
	 *         with this document id in the resulting postings list will not be
	 *         added.
	 */
	public static PostingsList union(PostingsList pl1, PostingsList pl2) {
		PostingsList result = new PostingsList();
		if (pl1 != null) {
			if (pl2 != null) {
				result.entries.addAll(pl1.entries);
				result.entries.addAll(pl2.entries);
				return result;
			} else { // pl1 != null, pl2 == null
				return pl1;
			}
		} else {
			if (pl2 != null) { // pl1 == null, pl2 != null
				return pl2;
			} else {
				return result; // return empty postings list if both parameters
								// are null
			}
		}
	}

	/**
	 * @param pl2
	 *            second posting list to merge
	 * @param pl1
	 *            first posting list to merge Unlike {@code union}, complete,
	 *            'deep', merging method that not only creates a binary union,
	 *            but also considers other fields of {@code PostingsList.Entry}.
	 *            When two entries in different postings lists have same
	 *            document id and are being merged, their term frequencies are
	 *            also added.
	 */
	public static PostingsList merge(PostingsList pl1, PostingsList pl2) {
		PostingsList result = new PostingsList();
		if (pl1 != null) {
			if (pl2 != null) {
				/*
				 * An attempt to slightly increase peformance by iterating over
				 * the smallest list. The idea is that, supposedly, fast addAll
				 * will handle the larger list, while leaving the shorter one
				 * for mergeWith to iterate over. Should work if time complexity
				 * addAll is less than O(n) and this.entries is a TreeSet and
				 * time complexity of TreeSet's floor function is less than O(n)
				 * [see getEntryByDocId for reasons]. otherwise no performace
				 * increase is gained.
				 */
				if (pl1.size() > pl2.size()) {
					result.entries.addAll(pl1.entries);
					result.mergeWith(pl2);
				} else {
					result.entries.addAll(pl2.entries);
					result.mergeWith(pl1);
				}
				return result;
			} else { // pl1 != null, pl2 == null
				return pl1;
			}
		} else {
			if (pl2 != null) { // pl1 == null, pl2 != null
				return pl2;
			} else {
				return result; // return empty postings list if both parameters
								// are null
			}
		}
	}

	/**
	 * 
	 * @param other
	 *            the other postin list to merge
	 */
	private void mergeWith(PostingsList other) {
		for (Entry entry : other) {
			// if the entry from other postings list exists in this one,
			// increase
			// tf of this entry by tf of other
			if (this.entries.contains(entry)) {
				Entry fromEntries = getEntryByDocId(entry.docId);
				fromEntries.mergeWith(entry);

				// else add other entry to this postings list
			} else {
				this.entries.add(entry);
			}
		}
	}

	/**
	 * ATTENTION: Can return null if no entries with requested docId were found.
	 * 
	 * @param docId
	 *            the wanted docId
	 * @return the entry with the respective docId
	 */
	public Entry getEntryByDocId(int docId) {
		Entry target = new Entry(docId);
		Entry result = null;
		if (this.entries instanceof TreeSet<?>) {
			Entry candidate = ((TreeSet<Entry>) this.entries).floor(target);
			if (candidate != null && candidate.equals(target)) {
				result = candidate;
			}
		} else {
			for (Entry candidate : this.entries) {
				if (candidate.equals(target)) {
					result = candidate;
					break;
				}
			}
		}
		return result;
	}

	public void addDocId(int docId) {
		// look up a doc id in the set,
		// insert if not found,
		// increment tf if found.
		if (!entries.contains(new Entry(docId))) {
			entries.add(new Entry(docId, 1));
		} else {
			Entry e = new Entry(docId);
			for (Entry entry : entries) {
				if (entry.equals(e)) {
					entry.termFrequency++;
				}
			}
		}
	}

	/**
	 * 
	 * @return the iterator of the entries. This will allow you to iterate
	 *         through the results.
	 */
	public Iterator<Entry> iterator() {
		return entries.iterator();
	}

	/**
	 * Nested class to take care of comparisons
	 */
	public static class Entry implements Comparable<PostingsList.Entry> {

		// should not be used outside of the PostingsList class.
		private Entry(int docId) {
			this.docId = docId;
			this.termFrequency = 0;
		}

		public Entry(int docId, double termFreq) {
			this.docId = docId;
			this.termFrequency = termFreq;
		}

		// accessors
		public int getDocId() {
			return docId;
		}

		public double getTermFrequency() {
			return termFrequency;
		}

		public void setDocId(int docId) {
			this.docId = docId;
		}

		// mutators
		public void setTermFrequency(double termFrequency) {
			this.termFrequency = termFrequency;
		}

		public int compareTo(PostingsList.Entry o) {
			return docId - o.docId;
		}

		@Override
		public int hashCode() {
			int hash = 5;
			hash = 61 * hash + this.docId;
			return hash;
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == null) {
				return false;
			}
			if (getClass() != obj.getClass()) {
				return false;
			}
			final Entry other = (Entry) obj;
			if (this.docId != other.docId) {
				return false;
			}
			return true;
		}

		@Override
		public String toString() {
			return docId + ":" + termFrequency;
		}

		private boolean mergeWith(Entry other) {
			if (this.docId == other.docId) {
				this.termFrequency += other.termFrequency;
				return true;
			}
			return false;
		}

		private int docId;
		private double termFrequency;
	}

	public void addEntry(Entry entry) {
		entries.add(entry);
	}

	public int size() {
		return entries.size();
	}

	public boolean isEmpty() {
		return entries.isEmpty();
	}

	public boolean contains(Entry entry) {
		return entries.contains(entry);
	}

	@Override
	public String toString() {
		return entries.toString();
	}

	private Set<Entry> entries;
}
